冬至到了~~~ 開始覺得有點冷了XD
前篇的Lab,應該算蠻淺顯易懂的吧!?
當作簡單介紹一下OpenOCD、Telnet、GDB操作的部分,順便簡單介紹一下OpenOCD Config file的使用!
本篇將深入介紹Config file中所使用到以及相關的Commands,並開始介紹相關的程式碼,
其他較進階的Commands會在往後的文章中慢慢提到!
  
  
  
這邊挑選一些OpenOCD常用的Command,並主要分成以下幾種,
而後面的幾個小節中,將一一介紹常用Command的使用
本節主要是介紹基本環境設定Command,讓OpenOCD能夠順利地完成初始化,
直到整個Debug Adapter設定完成為止
  
  
這邊主要是設定一些TCP/IP port number 相關的Command
  
設定GDB連接去OpenOCD的Port number,這是最常用到的Command之一,
不指定的話,預設號碼為3333。
值得注意的是,如果所設定的Port被占用的話,OpenOCD底層實作會直接結束.....(蠻令人...的設計...
另外,針對多重target(multi-core、multi-target)的部分,
一個target就會配上一個GDB Server,也就是說一個target就會有一個對應的GDB Port!
相關的程式碼可以在src/server/gdb_server.c 找到
static int gdb_target_add_one(struct target *target)
{
    if (strcmp(gdb_port, "disabled") == 0) {
        LOG_INFO("gdb port disabled");
        return ERROR_OK;
    }
    /*  one gdb instance per smp list */
    if ((target->smp) && (target->gdb_service))
        return ERROR_OK;
    int retval = gdb_target_start(target, gdb_port_next);
    if (retval == ERROR_OK) { //#譯註: 如果發現不成功,跳到最後,回傳ERROR_FAIL
        long portnumber;
        /* If we can parse the port number
         * then we increment the port number for the next target.
         */
        char *end;
        portnumber = strtol(gdb_port_next, &end, 0);
        if (!*end) {
            if (parse_long(gdb_port_next, &portnumber) == ERROR_OK) {
                free(gdb_port_next);
                if (portnumber) {
                    gdb_port_next = alloc_printf("%d", portnumber+1); //#譯註: 如果發現成功,則將Port number+1,
                                                                      //並持續將剩下的target放入
                } else {
                    /* Don't increment if gdb_port is 0, since we're just
                     * trying to allocate an unused port. */
                    gdb_port_next = alloc_printf("0");
                }
            }
        }
    }
    return retval;
}
如果需要讓OpenOCD持續將剩下的Target加入,或許可以考慮以下實作方式
static int gdb_target_add_one(struct target *target)
{
    if (strcmp(gdb_port, "disabled") == 0) {
        LOG_INFO("gdb port disabled");
        return ERROR_OK;
    }
    /*  one gdb instance per smp list */
    if ((target->smp) && (target->gdb_service))
        return ERROR_OK;
    while(1) {
        int retval = gdb_target_start(target, gdb_port_next);
        long portnumber = strtol(gdb_port_next, &end, 0);
        if (!*end) {
            if (parse_long(gdb_port_next, &portnumber) == ERROR_OK) {
                free((void *)gdb_port_next);
                gdb_port_next = alloc_printf("%d", portnumber+1);
            }
        }
        if (retval == ERROR_OK) {
            printf("The core #%d listens on %d.\n", (int)target->target_number, (int)portnumber);
            break;
        }
        else if ((portnumber+1) > 65535) {
            LOG_ERROR("gdb port number fail");
            printf("gdb port number fail!!\n");
            fflush(stdout);
            return ERROR_FAIL;
        }
    }
    return retval;
}
幾本上同上方gdb_port,比較需要注意的是,一個OpenOCD只會有一個Telnet Server,
但不同於GDB一個Port僅能容許一個connetion,Telent可同時接受多個Connection
詳細可以參考src/server/telnet_server.c中的實作
int telnet_init(char *banner)
{
    if (strcmp(telnet_port, "disabled") == 0) {
        LOG_INFO("telnet server disabled");
        return ERROR_OK;
    }
    struct telnet_service *telnet_service =
        malloc(sizeof(struct telnet_service));
    if (!telnet_service) {
        LOG_ERROR("Failed to allocate telnet service.");
        return ERROR_FAIL;
    }
    telnet_service->banner = banner;
    int ret = add_service("telnet", telnet_port, CONNECTION_LIMIT_UNLIMITED,    //#譯註: 這邊沒有限制連入數量!
        telnet_new_connection, telnet_input, telnet_connection_closed,
        telnet_service);
    if (ret != ERROR_OK) {
        free(telnet_service);
        return ret;
    }
    return ERROR_OK;
}
  
  
這邊主要是針對Debug Adapter去做設定,有些更為底層的設定會留待後續文章中介紹!
一般而言,OpenOCD的開發者針對有支援的Adapter,已經寫好預設的Config,
就如同上篇Lab所提到的一樣,直接拿來用即可!!
以下會針對較為通用的Commands來做說明
  
這邊主要是設定啟動Debug Adapter的Driver,以下是目前支援的Driver
Supported Interfaces
詳細支援的內容可以在 src/jtag/interfaces.c中找到 (如果需要新增Adapter支援的話,記得在這邊註冊)
  
這個Command主要是用來設定Adapter連結Target的時候,所使用的會高(快)速度,
以kHz為單位,所以如果設定adapter_khz 3000,就表示目前JTAG Clock為3MHz,
有些Adapter支援自動Scan Clock的功能,比如說Andes的AICE-mini、AICE-MCU...等等 繼續廣告,
詳細資料請參考Adapter相關的說明!
另外值得一提的是,如果adapter_khz後面帶0的話,就表示啟動RTCK,
可以參考(FAQ RTCK)[http://openocd.org/doc/html/FAQ.html#faqrtck]中有更詳細的說明
詳細的Source Code可以在src/jtag/core.c中找到
int adapter_init(struct command_context *cmd_ctx)
{
    ...中間省略...
    int requested_khz = jtag_get_speed_khz();
    int actual_khz = requested_khz;
    int jtag_speed_var = 0;
    retval = jtag_get_speed(&jtag_speed_var);
    if (retval != ERROR_OK)
        return retval;
    retval = jtag->speed(jtag_speed_var);
    if (retval != ERROR_OK)
        return retval;
    retval = jtag_get_speed_readable(&actual_khz);
    if (ERROR_OK != retval)
        LOG_INFO("adapter-specific clock speed value %d", jtag_speed_var);
    else if (actual_khz) {
        /* Adaptive clocking -- JTAG-specific */
        if ((CLOCK_MODE_RCLK == clock_mode)
                || ((CLOCK_MODE_KHZ == clock_mode) && !requested_khz)) {
            LOG_INFO("RCLK (adaptive clock speed) not supported - fallback to %d kHz"
            , actual_khz);
        } else
            LOG_INFO("clock speed %d kHz", actual_khz);
    } else
        LOG_INFO("RCLK (adaptive clock speed)");
    return ERROR_OK;
}
...中間省略...
static int jtag_set_speed(int speed)
{
    jtag_speed = speed;
    /* this command can be called during CONFIG,
     * in which case jtag isn't initialized */
    return jtag ? jtag->speed(speed) : ERROR_OK;
}
int jtag_config_khz(unsigned khz)
{
    LOG_DEBUG("handle jtag khz");
    clock_mode = CLOCK_MODE_KHZ;
    int speed = 0;
    int retval = adapter_khz_to_speed(khz, &speed);
    return (ERROR_OK != retval) ? retval : jtag_set_speed(speed);
}
int jtag_config_rclk(unsigned fallback_speed_khz)
{
    LOG_DEBUG("handle jtag rclk");
    clock_mode = CLOCK_MODE_RCLK;
    rclk_fallback_speed_khz = fallback_speed_khz;
    int speed = 0;
    int retval = jtag_rclk_to_speed(fallback_speed_khz, &speed);
    return (ERROR_OK != retval) ? retval : jtag_set_speed(speed);
}
以上到這邊,大至基本的連線設定都完成了,接下來的章節部分,
會開始進入Target相關的設定!!
  
  
  
TAPs全名為Test Access Ports,為JTAG中的核心部分,
而OpenOCD在連結Target的時候,必須要知道相關的設定才能夠正確的連接!
而TAPs的作用,在OpenOCD Developer's Guide中有簡單的解釋如下:
`
---摘自OpenOCD Developer's Guide中的10 TAP Declaration
`
簡單的來說就是做為各個Target上的存取界面,包含JTAG串接的訊號介面、JTAG狀態機和相關指令/資料暫存器等等,
其主要功能常用作Debug CPU Target、Flash燒錄、程式載入以及Boundary Scan測試。
  
  
主要用來定義新的JTAG TAPs,例如以下範例:
set _CHIPNAME riscv
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0xOOXX
由於<chipname>會經常被使用到,因此通常會用一個變數來指定,就像上例中的set _CHIPNAME riscv一樣。
再來是<tapname>的部分,OpenOCD定義以下類型的TAPs
For JTAG route controller---摘自OpenOCD Developer's Guide中的10 TAP Declaration  沒用過XD然後後面可以接上多個參數來設定這個TAPs,比方說
-irlen <NUMBER\>: 用來設定JTAG中instruction register的bit數,通常為4 or 5 bits,看硬體怎麼設計!
-expected-id <NUMBER\>: non-zero的32-bit IDCODE,用來讓OpenOCD判斷所連結的JTAG是否正確。
值得一提的地方在於,如果JTAG IDCODE全為0或是全為1的話,通常表示有問題,
相關檢查的Source Code在src/jtag/core.c中:
static bool jtag_examine_chain_check(uint8_t *idcodes, unsigned count)
{
    uint8_t zero_check = 0x0;
    uint8_t one_check = 0xff;
    for (unsigned i = 0; i < count * 4; i++) {
        zero_check |= idcodes[i];
        one_check &= idcodes[i];
    }
    /* if there wasn't a single non-zero bit or if all bits were one,
     * the scan is not valid.  We wrote a mix of both values; either
     *
     *  - There's a hardware issue (almost certainly):
     *     + all-zeroes can mean a target stuck in JTAG reset
     *     + all-ones tends to mean no target
     *  - The scan chain is WAY longer than we can handle, *AND* either
     *     + there are several hundreds of TAPs in bypass, or
     *     + at least a few dozen TAPs all have an all-ones IDCODE
     */
    if (zero_check == 0x00 || one_check == 0xff) {
        LOG_ERROR("JTAG scan chain interrogation failed: all %s",
            (zero_check == 0x00) ? "zeroes" : "ones");
        LOG_ERROR("Check JTAG interface, timings, target power, etc.");
        return false;
    }
    return true;
}
  
  
這個Command比較簡單,就是列出OpenOCD目前所設定的TAP內容,包含Tap名稱、狀態、IDCODE、IR Length等等資訊,
例如:
   TapName            Enabled IdCode     Expected   IrLen IrCap IrMask
-- ------------------ ------- ---------- ---------- ----- ----- ------
 0 omap5912.dsp          Y    0x03df1d81 0x03df1d81    38 0x01  0x03
 1 omap5912.arm          Y    0x0692602f 0x0692602f     4 0x01  0x0f
 2 omap5912.unknown      Y    0x00000000 0x00000000     8 0x01  0x03
---摘自OpenOCD Developer's Guide中的10 TAP Declaration
  
  
  
上面是針對連接Target所需要設定JTAG的部分,比較繁瑣一些,需要對JTAG有深入的了解!
而本節所設定的部分,比較針對Target的設定、狀態及Event控制相關!
  
  
類似#3.1 Command: jtag newtap 所提到的一樣,不過這邊主要是用來定義TAP上的Target,例如以下範例:
set _CHIPNAME riscv
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME riscv -chain-position $_TARGETNAME
另外由於<target_name>由於會經常被使用到,因此通常會用一個變數來指定,就像上例中的set _TARGETNAME $_CHIPNAME.cpu一樣。
最方便的使用方式是同TAP定義的一樣,比如上篇Lab所使用到的STM32-F429(tcl/target/stm32f4x.cfg):
set _CHIPNAME stm32f4x
set _TARGETNAME $_CHIPNAME.cpu
swj_newdap $_CHIPNAME cpu -irlen 4 ...後面省略
target create $_TARGETNAME cortex_m -chain-position $_TARGETNAME
再來是<type>的部分,OpenOCD裡面支援多種Target,詳細所支援的Target可以參考官方網站!
另外提醒一下,type名稱是由Source Code中所定義的,例如: stm32f4x在OpenOCD要使用"cortex_m",
NDS32的CPU依照指令集分成"nds32_v2", "nds32_v3" ... 各種不同的type 偷偷推廣一下!
也可以利用上篇文章中的Telent,使用target types來查詢目前OpenOCD所支援的Target
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
> target types
arm7tdmi arm9tdmi arm920t arm720t arm966e arm946e arm926ejs fa526 feroceon dragonite xscale cortex_m cortex_a 
cortex_r4 arm11 ls1_sap mips_m4k avr dsp563xx dsp5680xx testee avr32_ap7k hla_target nds32_v2 nds32_v3 nds32_v3m 
or1k quark_x10xx quark_d20xx riscv aarch64
[configparams]在這邊通常只需要定義這個Target屬於哪個Tap中,並用-chain-position <name>來表示,必須存在的設定!!
  
  
上面設定好<target_name>後,接下來就是詳細設定這個Target的細節部分!
常用的[configparams]分成以下幾種:
例如以下範例:
$_TARGETNAME configure -endian $_ENDIAN 
OpenOCD在進階使用的時候,可以利用target上一小部分的空間運作小小的program來加速debug或是其他功能,
這部分會留待往後Flash programming時候才會詳細講解,這邊先介紹常用的設定:
例如以下範例:
$_TARGETNAME configure -work-area-phys 0 -work-area-size 0x10000 -work-area-backup 1
  
  
OpenOCD充許在某些事件發生後,執行預先Hook好的那些指令,
例如以下的情況:
以下是節錄目前常用的Event:
例如以下範例(摘自stm32f4x.cfg):
$_TARGETNAME configure -event reset-start {
    # Reduce speed since CPU speed will slow down to 16MHz with the reset
    adapter_khz 2000
}
** 在Reset之前,先將Adapter的速度降至2 MHz **
以上介紹到這邊,OpenOCD應該要能夠順利地打起來,並正確地連接上板子!!
如果哪邊有設定錯誤,通常OpenOCD的Log中會用Error:表示,
可以先看看說明,對照Error message來除錯!
  
  
  
經過以上設定後,相信應該都能夠順利地了解OpenOCD如何從Init開始,一路經過Adapter設定、
JTAG TAPs設定,直到最後Target設定的部分!!
而本節主要是介紹一些平常在操作OpenOCD上,
  
  
這邊主要是延續上面#2 #3 #4的部分,介紹一些常用的Commands
  
查看目前OpenOCD所支援的Adapter清單,如果使用的是我在Day 02所提供的Script的話,
應該會出現以下的畫面:
> interface_list
The following debug interfaces are available:
1: ftdi
2: hla
因為我提供的Script中,其他的Adapter Support都被我關啦XDDDD
  
OpenOCD會定期去Polling(輪詢) Target的狀況,這邊可以開或關閉這項功能!
  
前篇Labs有提到了,這邊不重複說明!!打太多字,有點累了!
  
一樣## 4.1 Command: target create有介紹到了,不重複贅述!
  
  
這邊簡單介紹一下Server常用的幾個Commands
  
這個簡單,就是關掉Telnet的連線!!
  
應該不用解釋了吧XD!?
  
等待一定時間後,再繼續做下去!
通常用在上面所介紹## 4.3 Target Event中使用,用來控制時序!
如果使用[busy]的話,OpenOCD會使用busy-wait的方式,而不是Sleep
詳細可以參考src/helper/command.c和src/helper/log.c中的實作
# src/helper/command.c中
COMMAND_HANDLER(handle_sleep_command)
{
        bool busy = false;
        if (CMD_ARGC == 2) {
                if (strcmp(CMD_ARGV[1], "busy") == 0)
                        busy = true;
                else
                        return ERROR_COMMAND_SYNTAX_ERROR;
        } else if (CMD_ARGC < 1 || CMD_ARGC > 2)
                return ERROR_COMMAND_SYNTAX_ERROR;
        unsigned long duration = 0;
        int retval = parse_ulong(CMD_ARGV[0], &duration);
        if (ERROR_OK != retval)
                return retval;
        if (!busy) {                                                    
                int64_t then = timeval_ms();
                while (timeval_ms() - then < (int64_t)duration) {
                        target_call_timer_callbacks_now();
                        usleep(1000);                                   <-- 使用Sleep的方式
                }
        } else
                busy_sleep(duration);                                   <-- 使用busy-wait的方式
        return ERROR_OK;
}
# src/helper/log.c中
void busy_sleep(uint64_t ms)
{
        uint64_t then = timeval_ms();
        while (timeval_ms() - then < ms) {
                /*
                 * busy wait
                 */
        }
}
這邊比較特別一點,可以關掉所有OpenOCD所支援的Server和斷開所有連線!
比方說: Telnet、GDB、其他自訂的Server
  
上篇Lab中的#2.2.2 OpenOCD的使用,有稍微介紹到當OpenOCD執行的時候,
可以利用Level數字-3~3,從LOG_LVL_SILENT(-3)~LOG_LVL_DEBUG(3)來調整Log紀錄的項目,
當然,同樣的,這邊也可以進行設定!!
  
同樣的,上篇Lab中的#2.2.2 OpenOCD的使用,有稍微介紹到當OpenOCD執行的時候,
可以設定Log file的路徑,將預設會直接打印在OpenOCD的console上的Log,導向檔案中,方便除錯!
這邊也同樣是後面加上Log的路徑!!
  
  
這邊介紹的Commands主要跟Target狀態控制有關!
讓Target進入halt的狀態,OpenOCD會等待[ms]或是預設的5秒,
如果設定0ms的話,OpenOCD則不會進行等待!
  
讓Target在指定的位置做Resume,並從那個位置開始繼續執行下去!
如果不指定Address的話,則從Halt當下的位置開始執行下去!
  
讓Target進行Single-step,這邊後續文章會深入分析!
  
顧名思義,就是先Reset後再讓Target進入Free-Run的狀態!!
  
顧名思義,就是先Reset後再讓Target進入Halt的狀態!!
  
這邊就是沒加任何參數的Reset,預設行為同reset run
  
  
  
巴拉巴拉巴拉 介紹了這麼多Commands後,相信對OpenOCD的操作有更深入的了解了~!
下一篇開始,將會深入OpenOCD Source Code的世界中!一起加入Debugger的世界吧